package multidraw.model.server;

import java.awt.Color;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.server.UnicastRemoteObject;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.List;

import multidraw.model.Canvas;
import multidraw.model.CentralCanvasObserver;
import multidraw.model.Shape;
import multidraw.model.TextBox;
import multidraw.model.commands.Command;
import multidraw.tools.SelectorTool;
import multidraw.tools.Tool;

/**
 * This class serves as the local cache of the remote model. It helps 
 * accelerating the communication between the central server and the clients.
 * Reduce network traffic.
 * @author Cuong Bui.
 * @version 1.0.
 */
public class CanvasServer implements CanvasModel, Canvas {
	CoreData coreData;
	//List<CentralCanvasObserver> observers = new ArrayList<CentralCanvasObserver>();
	List<String> userList = new ArrayList<String>();
	List<String> activeUserList = new ArrayList<String>();
	
	Hashtable<String, ClientInfo> clients = 
		new Hashtable<String, ClientInfo>();	
	
	private Tool currentTool;
	private Color penColor = Color.black;
	
	//Shape selectedObject = null;
	int selectedObjectIndex = -1;
	
	public CanvasServer() {
		coreData = new CoreData();
		coreData.shapes = new ArrayList<Shape>();
		coreData.revisionID = 0;
	}
	
	public Shape getSelectedObject() {
		if (selectedObjectIndex >= 0 && selectedObjectIndex < coreData.shapes.size())
			return coreData.shapes.get(selectedObjectIndex);
		else return null;
	}
	
	public int getSelectedObjectIndex() {
		return selectedObjectIndex;
	}

	/*
	 * We need to add something to allow for a unique login process
	 * At this point the user will submit their login credentials
	 * It will identify if that name already exists or not
	 * For now this will be performed inside attach Observer
	 * eventually it will need to be moved outside to its own method
	 */
	
	public int attachObserver(CentralCanvasObserver o) throws RemoteException {
		
		/*
		 * This step allows a user that is logged in to connect to an existing session
		 * If the user is creating a session it will automatically attach that user
		 * to the created session and make them the owner
		 */
		
		
		ClientInfo clientInfo = new ClientInfo();
		clientInfo.clientID = o.getName();
		clientInfo.clientObject = o;
		clientInfo.updates = new ArrayList<Command>();
		clients.put(o.getName(), clientInfo);
		//observers.add(o);
		

		
		return 0;
	}
	
	public void notifyObservers() {
		for (ClientInfo o: clients.values()) {
			try {
				o.clientObject.updateModelContent();
			} catch (RemoteException e) {
				System.err.println(e);
			}
		}
	}
	
	public List<Shape> getData() {
		return coreData.shapes;
	}
	
	public void addShape(Shape s) {
		coreData.shapes.add(s);
		notifyObservers();
	}
	
	public void selectObject(int X, int Y) {
		for (int i = coreData.shapes.size() - 1; i >= 0; i--) {
			Shape s = coreData.shapes.get(i);
			
			if (s.isInsideSelectionalArea(X, Y)) {
				//selectedObject = s;
				selectedObjectIndex = i;
				//JOptionPane.showMessageDialog(null, "Selected");
				notifyObservers();
				return;
			}
		}
		
		selectedObjectIndex = -1; //no object selected
		//JOptionPane.showMessageDialog(null, "None Selected");
		notifyObservers();
	}
	
	public void setCurrentTool(Tool t) {
		currentTool = t;
		if (!(currentTool instanceof SelectorTool)) {
			selectedObjectIndex = -1; //deselect all objects.
		}
		notifyObservers();
	}
	
	public Tool getCurrentTool() {
		return currentTool;
	}
	
	public void clearCanvas() {
		coreData.shapes.clear();
		//fixed the null pointer exception in getSelectedObject.
		selectedObjectIndex = -1;
		//update all View
		notifyObservers();
	}
	//public void fillShape
	public Color getPenColor() {
		return penColor;
	}
	
	public void setPenColor(Color c) {
		System.out.println("Canvas color change ...");
		System.out.println("color = " + c);
		penColor = c;
		notifyObservers();
	}
	
	public void deleteSelectedObject() {
		if (selectedObjectIndex >= 0) {
			coreData.shapes.remove(selectedObjectIndex);
			selectedObjectIndex = -1;
		}
		notifyObservers();
	}
	
	/**
	 * Switch filling option of currently selected object
	 */
	public void switchSelectedObjectFilling(){
		if (getSelectedObject() != null) {
		    getSelectedObject().switchFilling();
		    notifyObservers();
		}
	}
	
	public static void main(String[] args) {
		try {
			CanvasServer obj = new CanvasServer();
			CanvasModel stub = (CanvasModel) UnicastRemoteObject.exportObject(obj, 0);
			
			//Bind the remote object's stub in the registry
			Registry registry = LocateRegistry.getRegistry();
			registry.bind("CanvasModel", stub);
			
			obj.getCoreData().shapes.clear();
			System.err.println("Server ready");			
		} catch (Exception e) {
			System.err.println("Server exception: " + e.toString());
			e.printStackTrace();
		}
	}

	@Override
	public void detachObserver(CentralCanvasObserver o) throws RemoteException {
		// TODO Auto-generated method stub
		
	}
	
	@Override
	public void applyCommand(Command command) throws RemoteException {
		// TODO Auto-generated method stub
		//command
		applyCommandToCoreData(command);
		
		//send it to other clients
		for (ClientInfo o: clients.values()) {
			o.updates.add(command);			
		}
		
		notifyObservers();
	}
	
	public void applyAddShape(Shape s) {
		coreData.shapes.add(s);
	}
	
	public List<Command> getUpdates(String clientID) {
		ClientInfo clientInfo = clients.get(clientID);
		if (clientInfo != null) {
			ArrayList<Command> updates = new ArrayList<Command>(clientInfo.updates);
			clientInfo.updates.clear();
			return updates;
		} else return null;
		
	}
	
	public CoreData getCoreData() {
		CoreData tmpCoreData = new CoreData();
		tmpCoreData.shapes = new ArrayList<Shape>(coreData.shapes);
		tmpCoreData.revisionID = coreData.revisionID;
		return tmpCoreData;
	}
	
	public void applyCommandToCoreData(Command c) {
		coreData.revisionID++;
		c.setCanvas(this);
		c.execute();
	}
	
	public long getNextID() {
		long id = 1;
		
		for (Shape s: coreData.shapes) {
			if (id < s.getShapeID()) id = s.getShapeID();
		}
		
		return id + 1;
	}
	
	public void applyAddChar(long shapeID, char ch) {
		for (Shape s: coreData.shapes) {
			if (s.getShapeID() == shapeID) {
				TextBox t = (TextBox) s;
				t.addChar(ch);
				return;
			}
		}
	}
	
	public List<Shape> getShapes() {
		return coreData.shapes;
	}
	
	public void setSelectedObjectIndex(int index) {
		//do nothing, because the server don't maintain this concept.
	}

	@Override
	public void applyAddUser(String name) {
		// TODO Auto-generated method stub
		
	}
}
